/**
 * 依赖管理等等
 */

const fs = require("fs")
const path = require("path")
const child_process = require("child_process");

const REPLACE_TEMPLATE = `######### NOTE ######### NOTE开始\n
本模板用于通过deps.sh put命令替换原来的依赖。
下面列出了原始依赖信息，格式为“name##url##(tag|branch|commitId)”。
1.首先需要替换依赖信息：
	需要找到对应的国内git仓库地址，替换里面的连接，要保证找到的仓库包含里面的tag、branch或commitId,
	可以通过gitee.com克隆国外的仓库。
2.其次需要运行deps.sh put命令用修改后的信息替换原来的依赖

\n######### DEPS ######### DEPS开始，请勿修改此行\n`;

class Tools {

	static tree_dir(base, relateDir, fn) {
		let curr = path.join(base, relateDir);
		fs.readdirSync(curr).forEach(name => {
			let relateFile = path.join(relateDir, name).replace(/[\\]/g, "/");
			if (fs.statSync(path.join(curr, name)).isDirectory()) {
				this.tree_dir(base, relateFile, fn);
			} else {
				fn(relateFile);
			}
		})
	}

	static list_depends(base, relateDir) {
		const deps = [];
		this.tree_dir(base, relateDir, relateFile => {
			if (!relateFile.match(".*\.cmake$")) {
				return;
			}

			console.log("handling: " + relateFile);
			const content = fs.readFileSync(path.join(base, relateFile)) + "";
			let matchDepInfo = (fnArgs, fnStart, fnEnd) => {
				const nameMatch = /\([\s\r\n]*([\w-]+)/.exec(fnArgs);
				const gitMatch = /\n\s*#?\s*(GIT_REPOSITORY)\s+(.+)/.exec(fnArgs);
				const tagMatch = /\n\s*#?\s*GIT_TAG\s+(.+)/.exec(fnArgs);
				const urlMatch = /\n\s*#?\s*URL\s+(.+)/.exec(fnArgs);
				const hashMatch = /\n\s*#?\s*URL_HASH\s+(.+)/.exec(fnArgs);
				const gitUrl = gitMatch && gitMatch[2] || "";
				const gitTag = tagMatch && tagMatch[1] || "";
				const url = urlMatch && urlMatch[1] || "";
				const hash = hashMatch && hashMatch[1] || "";
				const name = nameMatch && nameMatch[1] || "";
				const gitName = gitUrl.replace(".git", "").split(/[\|/]/).reverse()[0];

				if (!tagMatch && !urlMatch) {
					console.error("no dependencies: " + relateFile);
				} else {
					deps.push({
						file: relateFile,
						range: [fnStart, fnEnd],
						name: name.trim(),
						gitName: gitName.trim(),
						gitUrl: gitUrl.trim(),
						gitTag: gitTag.trim(),
						url: url.trim(),
						hash: hash.trim()
					});
				}
			}

			const fnArgsMatch = /(OverridableFetchContent_Declare|FetchContent_Declare|ExternalProject_Add)[\s\r\n]*(\([\s\S]+?\))/g;
			let r;
			while (r = fnArgsMatch.exec(content)) {
				matchDepInfo(r[2], r.index, fnArgsMatch.lastIndex);
			}
		})
		// deps.forEach(d => {
		// 	let dist = `${depDir}/../dependencies/${d.name}`;
		// 	if (fs.existsSync(dist)) {
		// 		deps.push(...this.list_depends(depDir, dist));
		// 	}
		// })
		return deps;
	}

	static save_file(content, file) {
		fs.writeFileSync(file, content);
		console.log("saved file: " + file + " \n" + content);
	}

	static put_deps(newDeps, originDepsPath) {
		this.copy_dir(MODULES, LOCAL_MODULES);

		const deps = JSON.parse(fs.readFileSync(path.join(PATH, originDepsPath)) + "");
		newDeps.forEach((newDep, i) => {
			const dep = deps.find(d => d.name == newDep.name);
			const content = fs.readFileSync(path.join(LOCAL_MODULES, dep.file)) + "";
			const head = content.substring(0, dep.range[0]);
			const tail = content.substring(dep.range[1]);
			let fn = content.substring(dep.range[0], dep.range[1]);

			console.log(`put ${dep.file}: ${dep.name} => URL ${newDep.gitUrl || newDep.url}`)

			if (dep.gitUrl || dep.url) {
				fn = fn.replace(/(?!\n)\s*?#?\s*?GIT_REPOSITORY(.*?)\r?(?=\n)/g, ($0, $1) => {
					let ret = $0;
					if (!/#\s*GIT_REPOSITORY/.test(ret)) {
						ret = ret.replace("GIT_REPOSITORY", '#GIT_REPOSITORY');
					}

					if ($1.trim() == dep.gitUrl) {
						ret += "\nGIT_REPOSITORY " + newDep.gitUrl;
					}
					console.log("replace " + ret + ": " + !/#GIT_REPOSITORY/.test($0));
					return ret;
				});
				fn = fn.replace(/(?!\n)\s*?GIT_TAG(.*?)\r?(?=\n)/g, ($0, $1) => {
					let ret = $0;
					if (!/#\s*GIT_TAG/.test(ret)) {
						ret = ret.replace("GIT_TAG", '#GIT_TAG');
					}

					if ($1.trim() == dep.gitTag) {
						ret += "\nGIT_TAG " + newDep.gitTag;
					}
					return ret;
				});

				fn = fn.replace(/(?!\n)\s*?#?\s*?URL\b(.*?)\r?(?=\n)/g, ($0, $1) => {
					let ret = $0;
					if (!/#\s*URL/.test(ret)) {
						ret = ret.replace("URL", '#URL');
					}

					if ($1.trim() == dep.url) {
						ret += "\nGIT_REPOSITORY " + newDep.gitUrl;
					}
					return ret;
				});
				fn = fn.replace(/(?!\n)\s*?#?\s*?URL_HASH\b(.*?)\r?(?=\n)/g, ($0, $1) => {
					let ret = $0;
					if (!/#\s*URL_HASH/.test(ret)) {
						ret = ret.replace("URL_HASH", '#URL_HASH');
					}

					if ($1.trim() == dep.hash) {
						ret += "\nGIT_TAG " + newDep.gitTag;
					}
					return ret;
				});
			}

			fs.writeFileSync(path.join(LOCAL_MODULES, dep.file), head + fn + tail);
		});
		console.log("put done");
	}

	static copy_dir(src, dist) {
		console.log(`copying ${src} => ${dist} ...`);
		this.tree_dir(src, ".", relateFile => {
			let from = path.join(src, relateFile);
			let to = path.join(dist, relateFile);
			fs.mkdirSync(path.dirname(to), {
				recursive: true
			});
			fs.writeFileSync(to, fs.readFileSync(from));
		});
		console.log(`copying done.`);
	}

	static make_source_dir_def() {
		const gits = this.list_depends();
		const currentDir = path.join(__dirname, ".").replace(/\\/g, "\\\\")
		gits.forEach(i => {
			console.log(`set(${i.gitName}_SOURCE_DIR ${currentDir}/deps-base/${i.gitName})`)
			if (i.gitName != i.name)
				console.log(`set(${i.name}_SOURCE_DIR ${currentDir}/deps-base/${i.gitName})`)
		})
	}
}

var CmdLinesDef = [
	["-path", "tflitebuilder's path", true],
	["-modules", "tflitebuilder's modules", true],
	["deps", "dependencies manager", false, [
		["list", "list dependencies to deps.replace.txt"],
		["put", "use deps.replace.txt to replace origin dependencies"],
		["dl", "download dependencies to deps-base directory"],
		["dl-put", "replace origin dependencies to downloaded local source"]
	]],
	["xx", "dependencies manager", false, [
		["list", "list dependencies to deps.replace.txt"],
		["put", "use deps.replace.txt to replace origin dependencies"],
		["dl", "download dependencies to deps-base directory"],
		["dl-put", "replace origin dependencies to downloaded local source"]
	]]
];

function parseCmdLine(args, ctx = {
	offset: 0, currDef: CmdLinesDef, result: {}
}) {

	let arg, val, def;

	while (arg = args[ctx.offset++]) {
		if (arg.indexOf("=") != -1) {
			val = arg.split("=")[1];
			arg = arg.split("=")[0];
		}
		def = ctx.currDef.find((def) => arg == def[0]);
		if (!def) {
			if (ctx.currDef == CmdLinesDef) {
				console.error("invalid argument: " + arg);
				process.exit(-1);
			}
			ctx.offset--;
			return;
		}
		if (val && !def[2]) {
			console.error("invalid argument: " + val);
			process.exit(-1);
			return;
		}
		if (def[2]) {
			ctx.result[arg] = val || args[ctx.offset++];
		} else {
			ctx.result[arg] = true;
		}
		if (def[3] && def[3].length) {
			parseCmdLine(args, childCtx = {
				offset: ctx.offset,
				currDef: def[3],
				result: ctx.result[arg] = {}
			});
			ctx.offset = childCtx.offset
			continue;
		}
	}
	return ctx.result;
}

const Deps = {
	/**
	 * 从modules列出依赖
	 */
	list() {
		const deps = Tools.list_depends(MODULES, ".").sort((a, b) => a.name.length - b.name.length);
		const depsJson = JSON.stringify(deps);
		const depsStr = deps.map(d => d.name + "\t##\t" + (d.gitTag) + "\t##\t" + (d.gitUrl || d.url))
			.join("\r\n");

		Tools.save_file(depsJson, path.join(PATH, "deps.json"))
		Tools.save_file(REPLACE_TEMPLATE + depsStr, path.join(PATH, "deps.replace.txt"))
	},
	/**
	 * 将modules复制到local_modules并更新其依赖
	 * @param {*} file 
	 * @returns 
	 */
	_put(file) {
		let match = (fs.readFileSync(path.join(PATH, file)) + "")
			.split(/######### DEPS #########.*/);
		if (match.length < 2) {
			console.error(file + "文件格式错误，请重新生成");
			process.exit(-1);
			return;
		}
		match = match[1].split("\n")
			.map(l => l.trim())
			.filter(l => l.length)
		const newDeps = match.map(l => l.split('##'))
			.map(d => ({
				name: d[0].trim(),
				gitTag: d[1].trim(),
				gitUrl: d[2].trim(),
			}));

		Tools.put_deps(newDeps, "deps.json");
	},
	put() {
		this._put("deps.replace.txt");
	},
	async dl() {
		console.log("listing local deps ...");
		let allDeps = [];
		const rootDeps = Tools.list_depends(LOCAL_MODULES, "").sort((a, b) => a.name.length - b.name.length);
		console.log("downloading local deps ...");
		let downloadDeps = (deps) => {
			allDeps.push(...deps);
			const allCmds = deps.map(async d => {
				if (d.gitUrl) {
					let gitCmd = (cwd, args) => {
						return new Promise((o, x) => {
							let opts = {
								cwd: cwd,
								stdio: [
									process.stdin,
									process.stdout,
									process.stderr
								],
								env: process.env
							};
							proc = child_process.spawn("git", args, opts);
							proc.on("close", code => {
								if (code === 0) {
									o()
								} else {
									x(`git args: ${dist} => ${args}, exit code: ${code}`);
								}
							})
								.on("error", err => x(`git args: ${dist} => ${args}, exit err: ${err}`));
						});
					}
					let dist = `${MODULES}/../dependencies/${d.name}`;
					let proc;
					console.log("git checkout to " + dist);

					if (fs.existsSync(dist + "/.git")) {
						await gitCmd(dist, [
							"checkout",
							"-f"
						]);
					} else {
						await gitCmd(".", [
							"clone",
							d.gitUrl,
							dist,
							"-f"
						]);
					}
					// if (fs.existsSync(dist + "/.gitsubmodule")) {
					// 	await gitCmd(dist, [
					// 		"submodule",
					// 		"update",
					// 		"--init"
					// 	]);
					// }
					// let childDeps = Tools.list_depends(base, dist).sort((a, b) => a.name.length - b.name.length);
					// await downloadDeps(childDeps);
				} else {
					// TODO 下载 d.url
				}
			});
			return Promise.all(allCmds);
		}
		try {
			await downloadDeps(rootDeps);

			const depsStr = allDeps.map(d => `${d.name}\t##\t${d.gitTag}\t##\t${path.resolve(MODULES, "..").replace(/\\/g, '/')}/dependencies/${d.name}`)
				.join("\r\n");
			Tools.save_file(REPLACE_TEMPLATE + depsStr, "deps.dl.txt")

			console.error("\n\ndownload done.");
		} catch (err) {
			console.error("download failed ", err);
			process.exit(-1);
		}
	},
	dl_put() {
		this._put("deps.dl.txt");
	}
}

let MODULES = "modules";
let LOCAL_MODULES = "local_modules";

function main(args) {
	// console.log("cmd line: ", args.slice(2));
	cmdObj = parseCmdLine(args.slice(2));
	console.log("cmdObj: ", cmdObj);

	PATH = cmdObj["-path"] || __dirname
	MODULES = path.join(PATH, MODULES);
	LOCAL_MODULES = path.join(PATH, LOCAL_MODULES);

	if (cmdObj.deps) {
		if (cmdObj.deps.list)
			Deps.list();
		if (cmdObj.deps.put)
			Deps.put();
		if (cmdObj.deps.dl)
			Deps.dl();
		if (cmdObj.deps["dl-put"])
			Deps.dl_put();
	}
}
// Tools.make_git_command((tag, url) => "git submodule add -b " + tag + "\t" + url, "./git_add_submodule.bat");
// Tools.make_cmake_file_to_local();
// Tools.make_git_command((tag, url, name) => "rm -rf ./" + name + " & git clone " + url, "./git_checkout_modules.bat");
// console.log(Tools.list_cmake_file_git_info())
// Tools.make_source_dir_def()

console.log("run tools with args: ", process.argv)
main(process.argv)
// main("exe js deps dl -path patharg".split(" "))